"use strict";

type MapValue = Map<string | number, any>;
type StorageValue =
  null
  | string
  | number
  | Record<string, unknown>
  | any[]
  | MapValue;

interface FmStorage {
  [key: string]: any;

  __type__: string | null;
  __value__?: unknown;
  __expires__: number | null;
}

export interface DynamicStorageConfig {
  key?: string;
  encode?: Function;
  decode?: Function;
}


interface SetStorageConfig {
  key?: string;
  expires?: number | Date;
  beforeStorage?: Function;
}

export class DynamicStorage {
  public key;
  public encode;
  public decode;
  private fm: FmStorage = {
    __type__: null,
    __value__: null,
    __expires__: null,
  };

  constructor(
    readonly Storage: Storage,
    private readonly name: string,
    {key, encode, decode}: DynamicStorageConfig = {}
  ) {
    if (![window.localStorage, window.sessionStorage].includes(Storage)) {
      throw TypeError(`使用${this.constructor.name}类实例LS参数必须要传入Storage类型`);
    }

    this.Storage = Storage;
    this.name = name;
    this.key = key;

    if (encode && decode) {
      this.encode = encode;
      this.decode = decode;
    } else if (encode ?? decode) {
      console.warn("xzTip: 使用编码或解码功能时，两者必须同时指定执行体！");
    }
  }

  has(key?: string): boolean {
    return this.get(key) !== null;
  }

  /**
   *
   * @param key
   * @param native
   * @param ignorehint
   */
  get(key?: string,
      {native = false, ignorehint = false, ignoreExpires = false} = {}): any {
    let storage = this.Storage.getItem(this.getKey(key));
    // 解码存在则使用解码
    this.decode && storage && (storage = this.decode(storage));

    if (!ignoreExpires && this.isExpires(key)) {
      !ignorehint && console.warn("xzTip: 已过期数据，将返回空！");
      return null;
    }
    return this.parse(storage, native);
  }

  set(
    value: StorageValue,
    {key, beforeStorage, expires}: SetStorageConfig = {}
  ) {
    const fkey = this.getKey(key);
    let storage = JSON.stringify(this._transverter(value, expires));

    // 存储之前钩子
    beforeStorage && beforeStorage(fkey, storage);
    // 存储之前检查
    // 解码存在则使用解码
    this.encode && (storage = this.encode(storage));
    this.Storage.setItem(fkey, storage);
  }

  remove(key?: string) {
    this.Storage.removeItem(this.getKey(key));
  }

  add<T = unknown>(data: T, key?: string) {
    // 解析并获取数据
    const storage = this.get(key);
    if (!storage) {
      return console.warn(`请先使用set方法存储数据`);
    }

    // 根据存储数据源类型处理添加数据
    switch (true) {
      case storage?.constructor === Object:
        Object.assign(storage, data);
        break;
      case storage?.constructor === Map:
        let keyValues: [unknown, unknown][] = [];
        if (data instanceof Map) {
          keyValues = Array.from(data.entries());
        } else {
          keyValues = Object.entries(data as Object);
        }
        for (const [k, v] of keyValues) {
          storage.set(k, v);
        }
        break;
      case Array.isArray(storage):
        storage.push(data);
        break;
      default:
        return console.warn(`很遗憾，所存储的数据类型为${typeof storage}，不支持该方法 `);
    }

    // 转换并存储数据
    this.set(storage, {key});
  }

  pop<T = unknown>(index: T, key?: string) {
    // 解析并获取数据
    const storage = this.get(key);
    if (!storage) {
      return console.warn(`请先使用set方法存储数据`);
    }

    // 根据存储数据源类型处理添加数据
    switch (true) {
      case storage?.constructor === Object:
        delete storage[index];
        break;
      case storage?.constructor === Map:
        storage.delete(index);
        break;
      case Array.isArray(storage):
        storage.splice(index as number, 1);
        break;
      default:
        return console.warn(`很遗憾，所存储的数据类型为${typeof storage}，不支持该方法 `);
    }

    // 转换并存储数据
    this.set(storage, {key});
  }

  getKey(key?: string, warn: boolean = true) {
    if (!key && this.key) {
      return `${this.name}:${this.key}`;
    }
    if (!key && warn) {
      console.warn(`xzTip: 并没有为${this.constructor.name}生成实例传入默认key，请在调用时传入key！`);
    }
    return `${this.name}:${key}`;
  }

  // 生成过期时间戳
  private generateExpiresStamp(stamp: SetStorageConfig['expires']): number | null {
    if (!stamp) {
      return null;
    }
    if (["string", "number"].includes(typeof stamp)
      && /^\d{10,}$/.test(String(stamp))) {
      return stamp as number;
    }
    if (stamp instanceof Date) {
      return stamp.getTime();
    }

    // 天数换算毫秒
    return (new Date()).getTime() + stamp * 24 * 60 * 60 * 1000;
  }

  /**
   * 转换器，将转换为内部数据类型格式
   * @param value 原始数据值
   * @param expires 过期时间，天数和日期类型
   * @private
   */
  private _transverter(
    value: StorageValue,
    expires: SetStorageConfig['expires']
  ): FmStorage {
    const ctx: FmStorage = {
      ...this.fm,
      __type__: typeof value,
      __expires__: this.generateExpiresStamp(expires)
    };
    let storage: StorageValue = value;

    // 特殊存储类型数据解析处理
    switch (true) {
      // Map类型处理
      case value?.constructor === Map:
        ctx.__type__ = "map";
        storage = {};
        for (const [k, v] of (value as MapValue).entries()) {
          storage[k] = v;
        }
        break;

      case value?.constructor === Object:
        storage = value;
        break;

      case value?.constructor === Array:
        ctx.__type__ = "array";
        storage = value;
        break;
    }
    ctx.__value__ = storage;

    return ctx;
  }

  /**
   * 解析器，将内部数据类型解析为原始数据
   * @param rawStorage 内部数据类型源
   * @private
   */
  private _resolver(rawStorage: Required<FmStorage>): FmStorage | unknown {
    const {__type__, __value__} = rawStorage;

    switch (__type__) {
      case "map":
        return new Map(Object.entries(__value__ as object));

      case "object":
        return __value__;
    }
    return __value__;
  }

  isExpires(key?: string) {
    const {__expires__} = this.get(key,
      {native: true, ignoreExpires: true}) ?? {};
    if (!__expires__) {
      return false;
    }
    return (new Date).getTime() >= __expires__;
  }

  parse(source?: string | null, native: boolean = false) {
    if (!source) {
      return source;
    }
    try {
      const raw = JSON.parse(source);
      if (native) {
        return raw;
      }
      return this._resolver(raw);
    } catch {
      console.error("Json解析异常");
    }
  }
}


export class DynamicSStorage extends DynamicStorage {
  constructor(...args: [string, DynamicStorageConfig]) {
    super(window.sessionStorage, ...args);
  }
}


export class DynamicLStorage extends DynamicStorage {
  constructor(...args: [string, DynamicStorageConfig]) {
    super(window.localStorage, ...args);
  }
}
